package frontend.cehandler;

import frontend.cehandler.compilationerrors.CompilationError;
import frontend.cehandler.compilationerrors.CECause;
import frontend.cehandler.compilationerrors.CEType;
import frontend.lexer.token.Token;
import frontend.parser.node.Node;
import utils.IOUtils;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;

public class CEHandler {
    private final PrintWriter printWriter;
    private final PrintWriter debugWriter;
    private final String inputFilePath;
    private final ArrayList<String> sourceCodeList;
    private final ArrayList<CompilationError> compilationErrors = new ArrayList<>();
    private static final String COLOR_START = "\033[31m";
    private static final String COLOR_END = "\033[0m";
    public CEHandler(String inputFilePath, ArrayList<String> sourceCodeList, PrintWriter printWriter, PrintWriter debugWriter) {
        this.inputFilePath = inputFilePath;
        this.sourceCodeList = sourceCodeList;
        this.printWriter = printWriter;
        this.debugWriter = debugWriter;
    }
    public CEHandler() {
        this.printWriter = this.debugWriter = IOUtils.NULL_PRINT_WRITER;
        this.inputFilePath = null;
        this.sourceCodeList = null;
    }

    public String getInputFilePath() {
        return inputFilePath;
    }
    public void printAll() {
        Collections.sort(compilationErrors);
        for (CompilationError compilationError : compilationErrors) {
            printWriter.println(compilationError.toString());
            debugWriter.println(getCEDetail(compilationError));
        }
    }

    public String getCEDetail(CompilationError compilationError) {
        StringBuilder stringBuilder = new StringBuilder();

        CECause errorCause = compilationError.getErrorCause();
        CEType errorType = compilationError.getErrorType();
        String message = compilationError.getMessage();
        int lineNumStart = compilationError.getLineNumStart();
        int lineNumEnd = compilationError.getLineNumEnd();
        int linePosStart = compilationError.getLinePosStart();
        int linePosEnd = compilationError.getLinePosEnd();
        Token causedToken = compilationError.getCausedToken();
        Node causedNode = compilationError.getCausedNode();

        stringBuilder.append(String.format(
                """
                        at (%s:%d:%d)
                        Compile Error: %s: %s
                        Caused in %s with info Token:[%s], Node:[%s]""",
                inputFilePath,
                lineNumStart,
                linePosStart,
                errorType,
                message,
                errorCause,
                causedToken,
                causedNode
        ));

        if (lineNumStart <= 0) {
            return stringBuilder.toString();
        }
        stringBuilder.append("\n");

        if (lineNumStart == lineNumEnd) {
            String line = sourceCodeList.get(lineNumStart - 1);
            stringBuilder
                    .append(String.format("%5d |\t", lineNumStart))
                    .append(line.substring(0, linePosStart - 1))
                    .append(COLOR_START)
                    .append(line.substring(linePosStart - 1, linePosEnd - 1))
                    .append(COLOR_END)
                    .append(line.substring(linePosEnd - 1));
        }
        else {
            for (int curLineNum = lineNumStart; curLineNum <= lineNumEnd; curLineNum++) {
                String line = sourceCodeList.get(curLineNum - 1);
                stringBuilder.append(String.format("%5d |\t", curLineNum));
                if (curLineNum == lineNumStart) {
                    stringBuilder
                            .append(line.substring(0, linePosStart - 1))
                            .append(COLOR_START)
                            .append(line.substring(linePosStart - 1))
                            .append(COLOR_END)
                            .append("\n");
                }
                else if (curLineNum == lineNumEnd) {
                    stringBuilder
                            .append(COLOR_START)
                            .append(line.substring(0, linePosEnd - 1))
                            .append(COLOR_END)
                            .append(line.substring(linePosEnd - 1));
                }
                else {
                    stringBuilder
                            .append(COLOR_START)
                            .append(line)
                            .append(COLOR_END)
                            .append("\n");
                }
            }
        }
        return stringBuilder.toString();
    }

    public CompilationError lexerError(CEType ceType, String message) {
        return addError(new CompilationError(
                CECause.LEXER,
                ceType,
                message,
                0,
                0,
                0,
                0,
                null,
                null
        ));
    }
    public CompilationError lexerError(CEType ceType, String message,
                                          int lineNumStart, int lineNumEnd,
                                          int linePosStart, int linePosEnd) {
        return addError(new CompilationError(
                CECause.LEXER,
                ceType,
                message,
                lineNumStart,
                lineNumEnd,
                linePosStart,
                linePosEnd,
                null,
                null
        ));
    }

    public CompilationError lexerError(CEType ceType, String message, Token token) {
        return addError(new CompilationError(
                CECause.LEXER,
                ceType,
                message,
                token.getLineNum(),
                token.getLineNum(),
                token.getLinePosStart(),
                token.getLinePosEnd(),
                token,
                null
        ));
    }

    public CompilationError parserError(CEType ceType, String message, Token token, Node node) {
        return addError(new CompilationError(
                CECause.PARSER,
                ceType,
                message,
                token.getLineNum(),
                token.getLineNum(),
                token.getLinePosStart(),
                token.getLinePosEnd(),
                token,
                node
        ));
    }

    public CompilationError visitorError(CEType ceType, String message, Token token) {
        return addError(new CompilationError(
                CECause.VISITOR,
                ceType,
                message,
                token.getLineNum(),
                token.getLineNum(),
                token.getLinePosStart(),
                token.getLinePosEnd(),
                token,
                null
        ));
    }

    public CompilationError visitorError(CEType ceType, String message, Token token, Node node) {
        return addError(new CompilationError(
                CECause.VISITOR,
                ceType,
                message,
                token.getLineNum(),
                token.getLineNum(),
                token.getLinePosStart(),
                token.getLinePosEnd(),
                token,
                node
        ));
    }
    public CompilationError visitorError(CEType ceType, String message, Node node) {
        Token firstToken = node.getFirstToken();
        Token lastToken = node.getLastToken();
        return addError(new CompilationError(
                CECause.VISITOR,
                ceType,
                message,
                firstToken.getLineNum(),
                lastToken.getLineNum(),
                firstToken.getLinePosStart(),
                lastToken.getLinePosStart(),
                null,
                node
        ));
    }

    public CompilationError addError(CompilationError compilationError) {
        compilationErrors.add(compilationError);
//        printWriter.println(compilationError.toString());
//        debugWriter.println(compilationError.detail());
        return compilationError;
    }
    public void addErrors(ArrayList<CompilationError> compilationErrors) {
        compilationErrors.forEach(this::addError);
    }
    public void addErrors(CEHandler ceHandler) {
        addErrors(ceHandler.compilationErrors);
    }

    public ArrayList<CompilationError> getCompilationErrors() {
        return compilationErrors;
    }
    /* 以Token为报错目标，语法、语义分析专用 */


}
